TechNote - Editing Text

Friday, March 10, 2023

10:29 AM

As described by the OneNote XML Schema definition, the simplest representation of a line of text in OneNote can be expressed as:

 

<one:OE alignment="left" quickStyleIndex="1">

  <one:T><![CDATA[Hello World]]></one:T>

</one:OE>

 

Every paragraph of text is expressed as a OneNote Element, or <one:OE>. An element is comprised of one or more text runs, <one:T>. And a text run includes a CDATA.

 

Selected Text

Selected text is identified by looking for the selected attribute. In this example, the entire paragraph is selected, so there is a selected="all" attribute on the text run.

 

<one:OE alignment="left" quickStyleIndex="1" selected="partial">

  <one:T selected="all"><![CDATA[Hello World]]></one:T>

</one:OE>

 

Note that all ancestors of the selection will include the attribute selected="partial". This includes the immediate one:OE and the entire hierarchy back up to and including the containing one:Outline.

 

Complex Selections

When only part of a paragraph is selected, OneNote divides the paragraph into multiple text runs so it can apply the selected attribute to only the selected run, as shown here

 

<one:OE alignment="left" quickStyleIndex="4" selected="partial">

  <one:T selected="all"><![CDATA[Hello ]]></one:T>

  <one:T><![CDATA[World]]></one:T>

</one:OE>

 

πŸ““

There can only be one!

Within a single paragraph, there can only one contiguous selection. It can span multiple text runs but it can't skip text runs. This means that you can select text from the middle of a paragraph like

 

This is an example paragraph with a text selection in the midle of it.

 

But even using the OneMore Invert Selection command, you cannot create a selection like this:

 

This is an example paragraph with a text selection in the midle of it.   β† Not possible!

 

 

Empty Selection

There are a couple of exceptions to note. The first is the empty selection which indicates the current position of the text cursor, if the cursor is focused on the page.

 

<one:OE alignment="left" quickStyleIndex="4" selected="partial">

  <one:T><![CDATA[Hel]]></one:T>

  <one:T selected="all"><![CDATA[]]></one:T>

  <one:T><![CDATA[lo World]]></one:T>

</one:OE>

 

The paragraph is divided into multiple text runs, where the middle text run includes an empty CDATA[] indicating the cursor position. So when detecting selected runs, routines need to consider whether there is exactly on select run and whether its content is an empty string.

 

URLs and MathML

Another exception is when the text cursor is position within hyperlinked text or over a MathML equation. For example, given the following paragraph where we presume the text cursor is placed immediately after the word "Page":

 

The Page| class contains…

 

The XML looks like this (the URL is truncated for simplicity)

 

<one:OE alignment="left" quickStyleIndex="3" selected="partial">

  <one:T><![CDATA[The ]]></one:T>

  <one:T selected="all"><![CDATA[<a href="https://...">Page class</a>]]></one:T>

  <one:T><![CDATA[ contains...]]></one:T>

</one:OE>

 

Notice that the entire URL CDATA is wrapped in a text run decorated with selected="all" even though the selection range is actually of zero length. The OneMore Page class detects this case and sets the SelectionScope to Empty even though the full CDATA will be returned as the selected text.

 

Compare that to actually selecting the full URL:

 

The Page class contains…

 

The XML looks exactly the same as it does above!

 

<one:OE alignment="left" quickStyleIndex="3" selected="partial">

  <one:T><![CDATA[The ]]></one:T>

  <one:T selected="all"><![CDATA[<a href="https://...">Page class</a>]]></one:T>

  <one:T><![CDATA[ contains...]]></one:T>

</one:OE>

 

OneNote doesn't project these two cases differently in the XML. So OneNote needs to treat them the same.

 

How OneMore Edits Text

OneMore has many internal routines to parse complex structures in the page XML. The Page class contains most of these routines including:

 

 

 

The routines most commonly used by OneMore commands are GetSelectedText and GetSelectectedElements.


The most complex routine, as you may guess, is
EditSelected. The two overrides of this routine invoke an edit function on the selected text. The selection may be either infered from the current cursor position or explicitly highlighted as a selected region. No assumptions are made as to the resultant content or the order in which parts of context are edited.

 

The callback Func accepts an XNode and returns an XNode. This is either an XText or a "span" XElement. The returned XNode can be either the original unmodified, the original modified, or a new XNode. Regardless, the returned XNode will replace the current XNode in the content.

 

The EditSelected routines take it one step further to emulate functionality similar to the way Microsoft Word lets you modify text. If the cursor is placed on a word, and that word is expressed as multiple text runs due to varying styling, EditSelected will apply the Func to the entire word. If there is a selection range greater than zero then the routine will apply the Func to the entire selection even if that means splitting treatments in the middle of a word.

 

The SelectionScope property gets the most recently known selection state where none means unknown, all means there is an obvious selection region, and partial means the selection region is zero.

 

The SelectionSpecial property gets an indication that the text cursor is positioned over either a hyperlink or a MathML equation, both of which return zero-length selection ranges.

 

 

────────────────────────────────────────────────────────────────────────────────────────────────────

Page PlantUML (Refresh)

@startuml Page

skinparam defaultFontSize 9

class Page {

 +get_SelectionScope: SelectionScope

 +get_SelectionSpecial: bool

 +bool EditNode(XElement cursor, Func edit)

 +bool EditSelected(Func edit)

 +bool EditSelected(XElement root, Func edit)

 +XElement ExtractSelectedContent(out XElement firstParent)

 +IEnumerable GetSelectedElements(bool all = true)

 +string GetSelectedText()

}

@enduml

 

 

#omwiki #omdeveloper #omtechnote

 

Β© 2020 Steven M Cohn. All rights reserved.

Please consider a sponsorship or one-time donation to support ongoing development

 

Created with OneNote.